﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using iNet.Win32;

namespace iNet.Media.Imaging
{
	static unsafe class FreeImage
	{
		#region ColorType
		public enum ColorType
		{
			MinIsWhite = 0,
			MinIsBlack = 1,
			Rgb = 2,
			Palette = 3,
			Rgba = 4,
			Cmyk = 5,
		}
        #endregion
		#region Format
		public enum Format
		{
			Unknown = -1,
			Bmp = 0,
			Ico = 1,
			Jpeg = 2,
			Jng = 3,
			Koala = 4,
			Lbm = 5,
			Mng = 6,
			Pbm = 7,
			PbmRaw = 8,
			Pcd = 9,
			Pcx = 10,
			Pgm = 11,
			PgmRaw = 12,
			Png = 13,
			Prm = 14,
			PrmRaw = 15,
			Ras = 16,
			Targa = 17,
			Tiff = 18,
			Wbmp = 19,
			Psd = 20,
			Cut = 21,
			Xbm = 22,
			Xpm = 23,
			Dds = 24,
			Gif = 25,
			Hdr = 26,
			FaxG3 = 27,
			Sgi = 28,
			Exr = 29,
			J2k = 30,
			Jp2 = 31,
			Pfm = 32,
			Pict = 33,
			Raw = 34,
		}
        #endregion
		#region FreeImageIO
		[StructLayout(LayoutKind.Sequential)]
		public struct FreeImageIO
		{
			[MarshalAs(UnmanagedType.FunctionPtr)]
			public ReadProc read_proc;
			[MarshalAs(UnmanagedType.FunctionPtr)]
			public WriteProc write_proc;
			[MarshalAs(UnmanagedType.FunctionPtr)]
			public SeekProc seek_proc;
			[MarshalAs(UnmanagedType.FunctionPtr)]
			public TellProc tell_proc;
		}
		#endregion
		#region ImageType
		public enum ImageType
		{
			Unknown = 0,
			Bitmap = 1,
			UInt16 = 2,
			Int16 = 3,
			UInt32 = 4,
			Int32 = 5,
			Float = 6,
			Double = 7,
			Complex = 8,
			Rgb16 = 9,
			Rgba16 = 10,
			RgbF = 11,
			RgbaF = 12,
		}
        #endregion
		#region JpegOperation
		public enum JpegOperation
		{
			None = 0,
			FlipX = 1,
			FlipY = 1,
			Transpose = 3,
			Transverse = 4,
			Rotate90 = 5,
			Rotate180 = 6,
			Rotate270 = 7,
		}
		#endregion
		#region LoadFlags
		[Flags]
		public enum LoadFlags
		{
			NoPixels = 0x8000,
			Default = 0x0,
			GifLoad256 = 0x1,
			GifPlayback = 0x2,
			IcoMakeAlpha = 0x1,
			JpegFast = 0x1,
			JpegAccurate = 0x2,
			JpegCmyk = 0x4,
			JpegGrayscale = 0x10,
			PcdBase = 0x1,
			PcdBaseDiv4 = 0x2,
			PcdBaseDiv16 = 0x3,
			PngIgnoreGamma = 0x1,
			PsdCmyk = 0x1,
			PsdLab = 0x2,
			RawPreview = 0x1,
			RawDisplay = 0x2,
			RawHalfSize = 0x4,
			TargaLoadRgb888 = 0x1,
			TiffCmyk = 0x1,
		}
        #endregion
		#region ReadProc
		public delegate int ReadProc(IntPtr buffer, int size, int count, IntPtr handle);
		#endregion
		#region SeekProc
		public delegate int SeekProc(IntPtr handle, int offset, int origin);
		#endregion
		#region TellProc
		public delegate int TellProc(IntPtr handle);
		#endregion
		#region ToneMappingOperator
		public enum ToneMappingOperator
		{
			Drago03 = 0,
			Reinhard05 = 1,
			Fattal02 = 2,
		}
        #endregion
		#region WriteProc
		public delegate int WriteProc(IntPtr buffer, int size, int count, IntPtr handle);
		#endregion
		#region 型別建構子
		static FreeImage()
		{
			Initialize(false);
		}
        #endregion
		#region AdjustBrightness
		[DllImport("FreeImage", EntryPoint = "FreeImage_AdjustBrightness")]
		public static extern bool AdjustBrightness(IntPtr dib, double percentage);
		#endregion
		#region AdjustContrast
		[DllImport("FreeImage", EntryPoint = "FreeImage_AdjustContrast")]
		public static extern bool AdjustContrast(IntPtr dib, double percentage);
		#endregion
		#region AdjustGamma
		[DllImport("FreeImage", EntryPoint = "FreeImage_AdjustGamma")]
		public static extern bool AdjustGamma(IntPtr dib, double gamma);
		#endregion
		#region Clone
		[DllImport("FreeImage", EntryPoint = "FreeImage_Clone")]
		public static extern IntPtr Clone(IntPtr dib);
		#endregion
		#region CreateFreeImageIO
		public static FreeImageIO CreateFreeImageIO()
		{
			FreeImageIO io = new FreeImageIO();
			io.read_proc = DefaultReadProc;
			io.write_proc = DefaultWriteProc;
			io.seek_proc = DefaultSeekProc;
			io.tell_proc = DefaultTellProc;
			return io;
		}
        #endregion
		#region DefaultReadProc
		static int DefaultReadProc(IntPtr buffer, int size, int count, IntPtr handle)
		{
			try
			{
				//取得資料流
				GCHandle gcHandle = GCHandle.FromIntPtr(handle);
				Stream stream = (gcHandle.Target as Stream);
				if (stream == null)
					return 0;

				//讀取資料
				count *= size;
				byte[] localBuffer = new byte[count];
				int result = stream.Read(localBuffer, 0, count);

				//複製資料
				if (result > 0)
					Marshal.Copy(localBuffer, 0, buffer, result);
				return result;
			}
			catch
			{
				return 0;
			}
		}
        #endregion
		#region DefaultSeekProc
		static int DefaultSeekProc(IntPtr handle, int offset, int origin)
		{
			try
			{
				//取得資料流
				GCHandle gcHandle = GCHandle.FromIntPtr(handle);
				Stream stream = (gcHandle.Target as Stream);
				if (stream == null)
					return -1;

				//移動位置
				if (stream.CanSeek)
				{
					SeekOrigin seekOrigin = (SeekOrigin)origin;
					stream.Seek(offset, seekOrigin);
					return (int)stream.Position;
				}
				return -1;
			}
			catch
			{
				return -1;
			}
		}
		#endregion
		#region DefaultTellProc
		static int DefaultTellProc(IntPtr handle)
		{
			try
			{
				//取得資料流
				GCHandle gcHandle = GCHandle.FromIntPtr(handle);
				Stream stream = (gcHandle.Target as Stream);
				if (stream == null)
					return -1;

				//取得位置
				return (int)stream.Position;
			}
			catch
			{
				return -1;
			}
		}
		#endregion
		#region DefaultWriteProc
		static int DefaultWriteProc(IntPtr buffer, int size, int count, IntPtr handle)
		{
			try
			{
				//取得資料流
				GCHandle gcHandle = GCHandle.FromIntPtr(handle);
				Stream stream = (gcHandle.Target as Stream);
				if (stream == null)
					return 0;

				//複製資料
				byte[] localBuffer = new byte[size];
				Marshal.Copy(buffer, localBuffer, 0, count);

				//寫入資料
				stream.Write(localBuffer, 0, count);
				return count;
			}
			catch
			{
				return 0;
			}
		}
		#endregion
		#region FlipHorizontal
		[DllImport("FreeImage", EntryPoint = "FreeImage_FlipHorizontal")]
		public static extern bool FlipHorizontal(IntPtr dib);
		#endregion
		#region FlipVertical
		[DllImport("FreeImage", EntryPoint = "FreeImage_FlipVertical")]
		public static extern bool FlipVertical(IntPtr dib);
		#endregion
		#region GetBits
		[DllImport("FreeImage", EntryPoint = "FreeImage_GetBits")]
		public static extern IntPtr GetBits(IntPtr dib);
		#endregion
		#region GetBpp
		[DllImport("FreeImage", EntryPoint = "FreeImage_GetBPP")]
		public static extern int GetBpp(IntPtr dib);
        #endregion
		#region GetColorType
		[DllImport("FreeImage", EntryPoint = "FreeImage_GetColorType")]
		public static extern ColorType GetColorType(IntPtr dib);
		#endregion
		#region GetColorsUsed
		[DllImport("FreeImage", EntryPoint = "FreeImage_GetColorsUsed")]
		public static extern int GetColorsUsed(IntPtr dib);
		#endregion
		#region GetDotsPerMeterX
		[DllImport("FreeImage", EntryPoint = "FreeImage_GetDotsPerMeterX")]
		public static extern int GetDotsPerMeterX(IntPtr dib);
		#endregion
		#region GetDotsPerMeterY
		[DllImport("FreeImage", EntryPoint = "FreeImage_GetDotsPerMeterY")]
		public static extern int GetDotsPerMeterY(IntPtr dib);
		#endregion
		#region GetFormat
		public static bool GetFormat(IntPtr dib, out PixelFormat pixelFormat, out BitmapPalette palette)
		{
			//初始化參數
			pixelFormat = PixelFormats.Default;
			palette = null;

			//取得格式
			ImageType imageType = GetImageType(dib);
			ColorType colorType = GetColorType(dib);
			int bpp = GetBpp(dib);
			switch (imageType)
			{
				case ImageType.Bitmap:
					switch (colorType)
					{
						case ColorType.Rgb:
							switch (bpp)
							{
								case 16:
									pixelFormat = PixelFormats.Bgr565;
									break;
								case 24:
									pixelFormat = PixelFormats.Bgr24;
									break;
								default:
									return false;
							}
							break;
						case ColorType.Rgba:
							if (bpp == 32)
								pixelFormat = PixelFormats.Bgra32;
							else
								return false;
							break;
						default:
							return false;
					}
					break;
				case ImageType.Rgb16:
					switch (colorType)
					{
						case ColorType.Rgb:
							pixelFormat = PixelFormats.Rgb48;
							break;
						case ColorType.Rgba:
							pixelFormat = PixelFormats.Rgba64;
							break;
						default:
							return false;
					}
					break;
				default:
					return false;
			}

			//完成
			return true;
		}
        #endregion
		#region GetHeight
		[DllImport("FreeImage", EntryPoint = "FreeImage_GetHeight")]
		public static extern int GetHeight(IntPtr dib);
		#endregion
		#region GetImageType
		[DllImport("FreeImage", EntryPoint = "FreeImage_GetImageType")]
		public static extern ImageType GetImageType(IntPtr dib);
        #endregion
		#region GetStride
		[DllImport("FreeImage", EntryPoint = "FreeImage_GetPitch")]
		public static extern int GetStride(IntPtr dib);
        #endregion
		#region GetThumbnail
		[DllImport("FreeImage", EntryPoint = "FreeImage_GetThumbnail")]
		public static extern IntPtr GetThumbnail(IntPtr dib);
		#endregion
		#region GetWidth
		[DllImport("FreeImage", EntryPoint = "FreeImage_GetWidth")]
		public static extern int GetWidth(IntPtr dib);
        #endregion
		#region HasPixels
		[DllImport("FreeImage", EntryPoint = "FreeImage_HasPixels")]
		public static extern bool HasPixels(IntPtr dib);
        #endregion
		#region Initialize
		[DllImport("FreeImage", EntryPoint = "FreeImage_Initialise")]
		static extern void Initialize(bool load_local_plugins_only);
        #endregion
		#region JpegTransform
		[DllImport("FreeImage", EntryPoint = "FreeImage_JPEGTransformU")]
		public static extern bool JpegTransform([MarshalAs(UnmanagedType.LPWStr)]string src_file, [MarshalAs(UnmanagedType.LPWStr)]string dst_file, JpegOperation operation, bool perfect = false);
		#endregion
		#region Load
		[DllImport("FreeImage", EntryPoint = "FreeImage_LoadU", CharSet = CharSet.Unicode)]
		public static extern IntPtr Load(Format fif, string filename, LoadFlags flags = 0);
		#endregion
		#region LoadFromHandle
		[DllImport("FreeImage", EntryPoint = "FreeImage_LoadFromHandle")]
		public static extern IntPtr LoadFromHandle(Format fif, ref FreeImageIO io, IntPtr handle, LoadFlags flags = 0);
		#endregion
		#region ToBitmapSource
		public static BitmapSource ToBitmapSource(IntPtr dib)
		{
			//確認像素
			if (!HasPixels(dib))
				return null;

			//取得大小
			int width = GetWidth(dib);
			int height = GetHeight(dib);
			int stride = GetStride(dib);

			//確認大小
			if (width <= 0 || height <= 0 || stride <= 0)
				return null;

			//取得格式
			ImageType imageType = GetImageType(dib);
			ColorType colorType = GetColorType(dib);
			int bpp = GetBpp(dib);
			PixelFormat pixelFormat;
			switch (imageType)
			{
				case ImageType.Bitmap:
					switch (colorType)
					{
						case ColorType.Rgb:
							switch (bpp)
							{
								case 16:
									pixelFormat = PixelFormats.Bgr565;
									break;
								case 24:
									pixelFormat = PixelFormats.Bgr24;
									break;
								default:
									return null;
							}
							break;
						case ColorType.Rgba:
							if (bpp == 32)
								pixelFormat = PixelFormats.Bgra32;
							else
								return null;
							break;
						default:
							return null;
					}
					break;
				case ImageType.Rgb16:
					switch (colorType)
					{
						case ColorType.Rgb:
							pixelFormat = PixelFormats.Rgb48;
							break;
						case ColorType.Rgba:
							pixelFormat = PixelFormats.Rgba64;
							break;
						default:
							return null;
					}
					break;
				default:
					return null;
			}

			//取得解析度
			double dpiX = (GetDotsPerMeterX(dib) / 39.37);
			double dpiY = (GetDotsPerMeterY(dib) / 39.37);
			if (dpiX <= 0 || dpiY <= 0)
				return null;

			//取得像素資料
			IntPtr srcPixels = GetBits(dib);
			if (srcPixels == IntPtr.Zero)
				return null;
			
			//反轉像素資料
			int bufferSize = (stride * height);
			IntPtr buffer = Marshal.AllocHGlobal(bufferSize);
			if (buffer == IntPtr.Zero)
				return null;
			byte* srcLine = (byte*)srcPixels;
			byte* destLine = ((byte*)buffer + ((height - 1) * stride));
			for (int i = height; i > 0; --i, srcLine += stride, destLine -= stride)
				Kernel32.CopyMemory(destLine, srcLine, stride);

			//建立點陣圖
			WriteableBitmap bitmapSource;
			try
			{
				bitmapSource = new WriteableBitmap(width, height, dpiX, dpiY, pixelFormat, null);
				bitmapSource.WritePixels(new Int32Rect(0, 0, width, height), buffer, bufferSize, stride);
				bitmapSource.Freeze();
			}
			catch
			{
				return null;
			}
			finally
			{
				Marshal.FreeHGlobal(buffer);
			}

			//完成
			return bitmapSource;
		}
        #endregion
		#region ToneMapping
		[DllImport("FreeImage", EntryPoint = "FreeImage_ToneMapping")]
		public static extern IntPtr ToneMapping(IntPtr dib, ToneMappingOperator tmo, double first_param = 0, double second_param = 0);
		#endregion
		#region Unload
		[DllImport("FreeImage", EntryPoint = "FreeImage_Unload")]
		public static extern void Unload(IntPtr dib);
        #endregion
	}
	class FreeImageIoAdapter : IDisposable
	{
		#region 欄位
		readonly FreeImage.FreeImageIO _FreeImageIO;
		readonly GCHandle _GCHandle;
		Stream _Stream;
		readonly bool _OwnsStream;
        #endregion
		#region 建構子
		public FreeImageIoAdapter(Stream stream, bool ownsStream)
		{
			_Stream = stream;
			_GCHandle = GCHandle.Alloc(stream);
			_FreeImageIO = FreeImage.CreateFreeImageIO();
			_OwnsStream = ownsStream;
		}
        #endregion
		#region Dispose
		public void Dispose()
		{
			if (_Stream == null)
				return;
			_GCHandle.Free();
			if (_OwnsStream)
				_Stream.Close();
			_Stream = null;
		}
        #endregion
		#region FreeImageIO
		public FreeImage.FreeImageIO FreeImageIO
		{
			get
			{
				return _FreeImageIO;
			}
		}
		#endregion
		#region Handle
		public IntPtr Handle
		{
			get
			{
				return GCHandle.ToIntPtr(_GCHandle);
			}
		}
        #endregion
	}
}
